"""
音频轨道组件。
"""
import typing

import opuslib  # type: ignore

from ..base import BaseSession, BaseSessionType, BaseSessionParentType
from ..framework import dispatch, session_received
from ..utils import callback, FloatValue, Int32Value, from_any, to_any


@session_received
class AudioTrack(BaseSession):
    """
    用于播放音频数据。
    传输使用opus编码进行压缩，可让带宽减小到十分之一。
    """
    name = 'AudioTrack'
    sampleRate: int
    channels: int
    encoder: opuslib.Encoder
    rawWritten: int
    opusWritten: int

    @classmethod
    async def create(cls: typing.Type[BaseSessionType], parent: BaseSessionParentType, *args: typing.Any,
                     **kwargs: typing.Dict) -> BaseSessionType:
        """
        输入信号的采样率(Hz)，必须是8000、12000、16000、24000、或48000。
        使用方法：
        track = await AudioTrack.create(activity, 16000, 1)
        await track.write(pcm, n_frames)
        """
        if len(args) < 2:
            raise TypeError('Missing 1 required positional argument: `sampleRate` and `channels`.')
        if not args[0] in (8000, 16000, 24000, 48000,):
            raise ValueError(
                f'The sample rate `{args[0]}` is unsupported with the media player. '
                'Valid sample rate is 8000, 16000, 24000, 48000.')
        sample_rate = to_any(args[0], Int32Value)
        channels = to_any(args[1], Int32Value)
        return await super().create(parent,
                                    sample_rate, channels,
                                    **kwargs)

    async def on_create(self, sample_rate, channels, *args, **kwargs):
        """
        当设备端AudioTrack组件创建完毕，此方法将自动调用。
        通常不需要关注此方法的实现。
        """
        await super().on_create(sample_rate, channels, *args, **kwargs)
        self.sampleRate, self.channels = from_any(sample_rate, Int32Value), from_any(channels, Int32Value)
        self.encoder = opuslib.Encoder(fs=self.sampleRate, channels=self.channels, application="audio")
        self.rawWritten, self.opusWritten = 0, 0

    async def on_receive(self, msg):
        """
        组件的事件转发。
        """
        if msg.name == 'onWrite':
            dispatch(self.on_write(*msg.argv, sessionId=msg.session, futureId=msg.future))
        else:
            await super().on_receive(msg)

    @callback(bool)
    async def on_write(self, res, **kwargs):
        """
        为了对一个帧进行编码，必须正确地用音频数据的帧(2.5, 5, 10, 20, 40 or 60 ms)。
        使用采样率*时间(s)来计算nFrames的正确值，否则编码器将无法工作。
        例如使用16000采样率，60ms的输入数据，计算的nFrames=16000(Hz)*0.06(s)=960。
        如果数据成功写入设备并解码，此方法将自动调用，这意味着播放器开始播放。
        通常不需要关注此方法的实现。
        """
        session_id = kwargs['sessionId'] if 'sessionId' in kwargs else None
        if not session_id:
            return
        future_id = kwargs['futureId'] if 'futureId' in kwargs else 0
        self.set_action_result(session_id, future_id, res)

    async def write(self, data: bytes, n_frames: int):
        """
        往设备端组件写入播放数据，此方法会等待数据写入成功并解码（即将播放时）或者写入失败才返回。
        需要在play()之后调用。
        @data: 原始PCM音频数据。
        @nFrames: 写入多少帧。
        """
        buf = self.encoder.encode(data, n_frames)
        res = await self.send_action_until_return('write', to_any(buf), to_any(n_frames, Int32Value))
        if res:
            self.rawWritten = len(data)
            self.opusWritten = len(buf)
        return res

    async def stop(self):
        """
        停止当前播放。
        """
        return await self.send_action('stop')

    async def pause(self):
        """
        暂停当前播放。
        """
        dispatch(self.send_action('pause'))

    async def play(self):
        """
        开始播放。
        """
        dispatch(self.send_action('play'))

    async def set_speed(self, rate: float):
        """
        设置播放速度。
        """
        dispatch(self.send_action('setSpeed', to_any(rate, FloatValue)))

    async def set_volume(self, left: float, right: typing.Union[float, None] = None):
        """
        设置播放音量，值在0.0到1.0之间。
        如果传入一个参数，则同时控制两个声道；
        如果传入两个参数，则分别控制两个声道。
        """
        vol = right if right is not None else left
        dispatch(self.send_action('setVolume', to_any(left, FloatValue), to_any(vol, FloatValue)))
